﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using ActiveUp.Net.Mail;

namespace MailSizeLabeler
{
    class Program
    {
        const int THREAD_COUNT = 7;

        // All items mail box
        const string MAILBOX_GMAIL_ALL_MAIL = "[Gmail]/All Mail";

        const string IMAP_SERVER = "imap.gmail.com";
        const int IMAP_PORT = 993;

        static void Main(string[] args)
        {
            System.Console.WriteLine("Enter your e-mail:");
            string email = System.Console.ReadLine();
            System.Console.WriteLine("Enter your password:");
            string password = ReadPassword();
            System.Console.WriteLine();

            DateTime startTime = DateTime.Now;

            Imap4Client imap = new Imap4Client();

            List<RangeMailboxDef> rangeList = RangeMailboxDef.BuildRangeList();

            // Connect
            Log("Connecting...");
            imap.ConnectSsl(IMAP_SERVER, IMAP_PORT);

            // Login
            Log("Starting login");
            imap.Login(email, password);

            // Get the list of all mail boxes
            Log("Loading label list");
            imap.LoadMailboxes();

            // Create new mail boxes if needed
            VerifyAndCreateLabels(imap, rangeList);

            ProcessMailBox(imap, MAILBOX_GMAIL_ALL_MAIL, rangeList, email, password);

            System.Console.WriteLine("Run time: {0}", new TimeSpan(DateTime.Now.Ticks - startTime.Ticks));
        }

        private static void ProcessMailBox(Imap4Client imap, string sourceMailBoxName, List<RangeMailboxDef> rangeList, string email, string password)
        {
            Mailbox sourceMailBox = null;
            foreach (Mailbox mailbox in imap.AllMailboxes)
                if (mailbox.Name == sourceMailBoxName)
                    sourceMailBox = mailbox;

            if (sourceMailBox == null)
            {
                Log("Failed to find and process mailbox: {0}", sourceMailBoxName);
                return;
            }

            Log("Starting to process mailbox: {0} with {1} e-mails", sourceMailBox.Name, sourceMailBox.MessageCount);

            // Moving messages one by one - using multiple threads. (SEARCH didn't work see BUGBUG comment bellow
            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < THREAD_COUNT; i++)
            {
                Thread t = new Thread(new ParameterizedThreadStart(MessageProcessorThread));
                t.Start(new ThreadParams(email, password, sourceMailBox.Name, i));
                threads.Add(t);
            }

            foreach (Thread t in threads)
                t.Join();

            /*
            // Moving messages one by one. (SEARCH didn't work see BUGBUG comment bellow
            for (int i = 0; i < sourceMailBox.MessageCount; i++)
            {
                int size = sourceMailBox.Fetch.Size(i + 1);
                Log("Message size: {0}", size);
                foreach (RangeMailboxDef rangeDef in rangeList)
                    if (rangeDef.IsInRange(size) == true)
                    {
                        Log("Moving to: {0}", rangeDef.Name);
                        sourceMailBox.CopyMessage(i + 1, rangeDef.Name);
                    }
            }            
            */

            /*
            // BUGBUG: This doesn't work - it might be (a) a problem in the query or (b) Google did not 
            // implement IMAP SEARCH command arguments LARGER/SMALLER
            foreach (RangeMailboxDef rangeDef in rangeList)
            {
                string queryString = rangeDef.GetIMAPSearchQuery();

                Log("Processing range label: {0}", rangeDef.Name);
                int[] messages = sourceMailBox.Search(queryString);
                Log("Found {0} messages", messages.Length);
                foreach (int message in messages)
                {
                    sourceMailBox.CopyMessage(message, rangeDef.Name);
                    System.Console.Write(".");
                }
                if (messages.Length > 0)
                    System.Console.WriteLine();
            }
             */
        }

        class ThreadParams
        {
            public string Username;
            public string Password;
            public string MailboxName;
            public int ThreadID;

            public ThreadParams(string username, string password, string mailboxName, int threadId)
            {
                this.Username = username;
                this.Password = password;
                this.MailboxName = mailboxName;
                this.ThreadID = threadId;
            }
        }

        private static void MessageProcessorThread(object args)
        {
            ThreadParams threadParams = (ThreadParams)args;
            List<RangeMailboxDef> rangeList = RangeMailboxDef.BuildRangeList();

            Imap4Client imap = new Imap4Client();
            Log("{0}  Connecting...", threadParams.ThreadID);
            imap.ConnectSsl(IMAP_SERVER, IMAP_PORT);

            // Login
            Log("{0}  Starting login", threadParams.ThreadID);
            imap.Login(threadParams.Username, threadParams.Password);

            Mailbox sourceMailBox = imap.SelectMailbox(threadParams.MailboxName);

            for (int i = 0; i < sourceMailBox.MessageCount; i++)
            {
                if (i % THREAD_COUNT != threadParams.ThreadID)
                    continue;

                int size = sourceMailBox.Fetch.Size(i + 1);
                Log("{1}  Message size: {0}", size, threadParams.ThreadID);
                foreach (RangeMailboxDef rangeDef in rangeList)
                    if (rangeDef.IsInRange(size) == true)
                    {
                        Log("{1}  Moving to: {0}", rangeDef.Name, threadParams.ThreadID);
                        sourceMailBox.CopyMessage(i + 1, rangeDef.Name);
                    }
            }
        }

        private static void VerifyAndCreateLabels(Imap4Client imap, List<RangeMailboxDef> rangeList)
        {
            Dictionary<string, RangeMailboxDef> rangeDic = new Dictionary<string, RangeMailboxDef>();
            foreach (RangeMailboxDef rangeDef in rangeList)
                rangeDic.Add(rangeDef.Name, rangeDef);

            foreach (Mailbox mailbox in imap.Mailboxes)
            {
                if (rangeDic.ContainsKey(mailbox.Name))
                    rangeDic.Remove(mailbox.Name);
            }
            foreach (string newMailBox in rangeDic.Keys)
            {
                Log("Creating new label: {0}", newMailBox);
                imap.CreateMailbox(newMailBox);
            }
        }

        private static void Log(string format, params object[] args)
        {
            System.Console.WriteLine(format, args);
        }

        /// <summary>
        /// Code for this function taken from here:
        /// http://ryepup.unwashedmeme.com/blog/2007/07/05/reading-passwords-from-the-console-in-c/
        /// </summary>
        /// <returns></returns>
        public static string ReadPassword()
        {
            Stack<string> passbits = new Stack<string>();
            //keep reading
            for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
            {
                if (cki.Key == ConsoleKey.Backspace)
                {
                    //rollback the cursor and write a space so it looks backspaced to the user
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    Console.Write(" ");
                    Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                    passbits.Pop();
                }
                else
                {
                    Console.Write("*");
                    passbits.Push(cki.KeyChar.ToString());
                }
            }
            string[] pass = passbits.ToArray();
            Array.Reverse(pass);
            return string.Join(string.Empty, pass);
        }
    }
}
